﻿// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using System.Collections.Generic;
using System.Windows.Media;

using Nova.CodeDOM;

namespace Nova.UI
{
    /// <summary>
    /// The view model for a <see cref="CodeDOM.Expression"/>.
    /// </summary>
    public abstract class ExpressionVM : CodeObjectVM
    {
        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create a view model instance for the specified <see cref="CodeDOM.Expression"/>.
        /// </summary>
        protected ExpressionVM(Expression expression, Dictionary<CodeObject, CodeObjectVM> dictionary)
            : base(expression, dictionary)
        { }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// The underlying <see cref="CodeDOM.Expression"/> model.
        /// </summary>
        public Expression Expression
        {
            get { return (Expression)CodeObject; }
        }

        #endregion

        #region /* METHODS */

        /// <summary>
        /// Get the <see cref="ExpressionVM"/> on the right of the right-most <see cref="LookupVM"/> or <see cref="DotVM"/> (bypass any '::' and '.' prefixes).
        /// </summary>
        public virtual ExpressionVM SkipPrefixes()
        {
            return this;
        }

        #endregion

        #region /* RENDERING */

        public static readonly Brush StaticBorderBrush = Brushes.PaleGoldenrod;
        public static readonly Brush StaticBackgroundBrush = Brushes.LightYellow;

        public override Brush BorderBrush
        {
            get { return StaticBorderBrush; }
        }

        public override Brush BackgroundBrush
        {
            get { return StaticBackgroundBrush; }
        }

        protected internal override bool HasBorder()
        {
            return CodeRenderer.MaximizeBorders;
        }

        /// <summary>
        /// Render an expression, with optional border, parens, and comments.
        /// </summary>
        public override void Render(CodeRenderer renderer, RenderFlags flags)
        {
            Expression expression = Expression;
            int newLines = expression.NewLines;
            bool isPrefix = flags.HasFlag(RenderFlags.IsPrefix);
            if (!isPrefix && newLines > 0)
            {
                if (!flags.HasFlag(RenderFlags.SuppressNewLine))
                    renderer.NewLines(newLines, ParentVM);
            }
            else if (flags.HasFlag(RenderFlags.PrefixSpace))
                renderer.RenderText(" ", PUNC_BRUSH, ParentVM);

            bool increaseIndent = (flags.HasFlag(RenderFlags.IncreaseIndent) && !flags.HasFlag(RenderFlags.NoIncreaseIndent));
            bool hasBorder = (flags.HasFlag(RenderFlags.ForceBorder) || HasBorder()) && !flags.HasFlag(RenderFlags.NoBorder);
            if (hasBorder)
            {
                CreateBorder(renderer, flags);
                increaseIndent = true;  // Always increase the indent level for wrapped lines inside a border
            }

            RenderFlags passFlags = (flags & RenderFlags.PassMask);
            RenderBefore(renderer, passFlags | RenderFlags.IsPrefix);

            // Increase the indent level for any newlines that occur within the expression if the flag is set
            if (increaseIndent)
                renderer.IndentOnNewLineBegin(this);

            //bool hasParens = this is Operator && Parent is Operator && !(Parent is Cast) && !(Parent is ArgumentOperator && ((ArgumentOperator)Parent).HasArgumentParens);  // Show extra parens
            bool hasParens = expression.HasParens;
            if (hasParens)
                renderer.RenderText(Expression.ParseTokenStartGroup, PUNC_BRUSH, this);
            RenderExpression(renderer, passFlags | (flags & (RenderFlags.Attribute | RenderFlags.HasDotPrefix | RenderFlags.Declaration)));
            if (hasParens)
            {
                if (expression.IsEndFirstOnLine)
                    renderer.NewLine();
                renderer.RenderText(Expression.ParseTokenEndGroup, PUNC_BRUSH, this);
            }
            if (expression.HasTerminator && !StatementVM.HideTerminators && !flags.HasFlag(RenderFlags.Description))
                renderer.RenderText(Statement.ParseTokenTerminator, PUNC_BRUSH, this);
            if (!flags.HasFlag(RenderFlags.NoEOLComments))
                RenderEOLComments(renderer, flags);

            // Determine if the indent can be ended after rendering the annotations instead of before, so that any post-comments are indented
            // with the lines above them.  If there are any post compiler directives, we can't do this.
            bool hasPostCompilerDirectives = (Annotations != null && Annotations.Exists(delegate(AnnotationVM annotationVM) { return annotationVM is CompilerDirectiveVM; }));
            if (hasPostCompilerDirectives && increaseIndent)
                renderer.IndentOnNewLineEnd(this);

            RenderAfter(renderer, passFlags | (flags & RenderFlags.NoPostAnnotations));

            if (!hasPostCompilerDirectives && increaseIndent)
                renderer.IndentOnNewLineEnd(this);

            // If we have a border, return to the previous one
            if (hasBorder)
                renderer.ReturnToBorderParent(this);

            if (isPrefix)
            {
                // If this object is rendered as a child prefix object of another, then any whitespace is
                // rendered here *after* the object instead of before it.
                if (newLines > 0)
                    renderer.NewLines(newLines, ParentVM);
                else if (!flags.HasFlag(RenderFlags.NoSpaceSuffix))
                    renderer.RenderText(" ", PUNC_BRUSH, ParentVM);
            }
        }

        public abstract void RenderExpression(CodeRenderer renderer, RenderFlags flags);

        #endregion
    }
}
